/**
 * Sequential Content Manager
 *
 * Manages the MCP reflection->action workflow for AI assistant responses.
 *
 * This service orchestrates the complex flow where the AI model:
 * 1. Reflects on the user's request (thinking phase)
 * 2. Decides to use tools to gather information (action phase)
 * 3. Reflects on the tool results (analysis phase)
 * 4. Provides a final response incorporating the gathered data
 *
 * The sequential approach allows for more thoughtful and informed responses
 * compared to traditional single-pass generation, especially when external
 * tools or real-time data are involved.
 */

import { ServiceRegister } from "@main/shared/reflect";
import { TYPES } from "@main/shared/types";
import { extractErrorMessage } from "@main/utils/error-utils";
import logger from "@shared/logger/main-logger";
import type { UpdateMessageData } from "@shared/triplit/types";
import type { ToolCallResult } from "@shared/types/openai";
import type {
  ContentBlock,
  ReflectionContentBlock,
  SequentialMessageContent,
  ToolCallContentBlock,
} from "@shared/types/sequential-content";
import { inject, injectable } from "inversify";
import { nanoid } from "nanoid";
import { EventNames, sendToRenderer } from "./event-service";
import type { MessageService } from "./message-service";

@ServiceRegister(TYPES.SequentialContentManager)
@injectable()
export class SequentialContentManager {
  constructor(
    @inject(TYPES.MessageService) private messageService: MessageService,
  ) {}

  /**
   * Helper method to get a message by ID without requiring IPC event context.
   * Used internally since we're calling from the main process, not renderer.
   */
  private async getMessageById(messageId: string) {
    // Create a mock event object since we're calling internally
    const mockEvent = {} as Electron.IpcMainEvent;
    return await this.messageService.getMessageById(mockEvent, messageId);
  }

  /**
   * Helper method to update a message without requiring IPC event context.
   * Used internally since we're calling from the main process, not renderer.
   */
  private async updateMessage(
    messageId: string,
    updateData: UpdateMessageData,
  ) {
    // Create a mock event object since we're calling internally
    const mockEvent = {} as Electron.IpcMainEvent;
    return await this.messageService.updateMessage(
      mockEvent,
      messageId,
      updateData,
    );
  }

  /**
   * Sets up a message for sequential content by initializing the contentBlocks field.
   *
   * This prepares the message for the reflection->action workflow by:
   * - Creating an empty sequential content structure
   * - Marking the message as having sequential content
   * - Notifying the renderer to prepare for real-time block updates
   *
   * @param messageId - Database ID of the message to initialize
   * @param threadId - Thread ID for targeted renderer notifications
   */
  async initializeSequentialContent(
    messageId: string,
    threadId: string,
  ): Promise<void> {
    const initialContent: SequentialMessageContent = {
      blocks: [],
      isComplete: false,
    };

    await this.updateMessage(messageId, {
      contentBlocks: JSON.stringify(initialContent),
      hasSequentialContent: true,
    });

    sendToRenderer(EventNames.CONTENT_BLOCK_ADD, {
      threadId,
      messageId,
      blockId: "init",
      blockType: "reflection",
      order: 0,
    });
  }

  /**
   * Creates a new reflection block for AI model thinking/analysis.
   *
   * Reflection blocks represent the AI's internal reasoning process,
   * typically used for:
   * - Initial analysis of the user's request
   * - Processing and understanding tool execution results
   * - Final synthesis before providing the response
   *
   * @param messageId - Database ID of the message containing this block
   * @param threadId - Thread ID for targeted renderer notifications
   * @param initialContent - Optional starting content for the reflection
   * @returns The unique block ID for subsequent updates
   */
  async addReflectionBlock(
    messageId: string,
    threadId: string,
    initialContent = "",
  ): Promise<string> {
    const blockId = nanoid();
    const order = await this.getNextBlockOrder(messageId);

    const newBlock: ReflectionContentBlock = {
      id: blockId,
      type: "reflection",
      content: initialContent,
      timestamp: Date.now(),
      order,
      isComplete: false,
    };

    await this.addBlock(messageId, newBlock);

    sendToRenderer(EventNames.CONTENT_BLOCK_ADD, {
      threadId,
      messageId,
      blockId,
      block: newBlock, // Send the complete block data
    });

    sendToRenderer(EventNames.MCP_REFLECTION_START, {
      threadId,
      messageId,
      blockId,
    });

    return blockId;
  }

  /**
   * Update reflection block content (for streaming)
   */
  async updateReflectionContent(
    messageId: string,
    threadId: string,
    blockId: string,
    content: string,
    isComplete = false,
  ): Promise<void> {
    const sequentialContent = await this.getSequentialContent(messageId);
    if (!sequentialContent) return;

    const blockIndex = sequentialContent.blocks.findIndex(
      (b) => b.id === blockId,
    );
    if (blockIndex === -1) return;

    const block = sequentialContent.blocks[blockIndex];
    if (block.type !== "reflection") return;

    const updatedBlock: ReflectionContentBlock = {
      ...block,
      content,
      isComplete,
      timestamp: Date.now(),
    };

    sequentialContent.blocks[blockIndex] = updatedBlock;
    await this.saveSequentialContent(messageId, sequentialContent);

    sendToRenderer(EventNames.MCP_REFLECTION_CONTENT, {
      threadId,
      messageId,
      blockId,
      content,
      isComplete,
    });

    sendToRenderer(EventNames.CONTENT_BLOCK_UPDATE, {
      threadId,
      messageId,
      blockId,
      updateData: { content, isComplete },
    });
  }

  /**
   * Add a tool call block
   */
  async addToolCallBlock(
    messageId: string,
    threadId: string,
    toolCall: ToolCallResult,
  ): Promise<string> {
    const blockId = nanoid();
    const order = await this.getNextBlockOrder(messageId);

    logger.info("Adding tool call block", {
      messageId,
      threadId,
      blockId,
      toolName: toolCall.name,
      mcpServerId: toolCall.mcpServerId,
      status: toolCall.status,
    });

    const newBlock: ToolCallContentBlock = {
      id: blockId,
      type: "tool_call",
      toolCall,
      timestamp: Date.now(),
      order,
      isExecuting:
        toolCall.status === "pending" ||
        toolCall.status === "awaiting_approval",
    };

    await this.addBlock(messageId, newBlock);

    logger.info("Sending CONTENT_BLOCK_ADD event", {
      threadId,
      messageId,
      blockId,
      blockType: newBlock.type,
      toolCallMcpServerId: newBlock.toolCall.mcpServerId,
    });

    sendToRenderer(EventNames.CONTENT_BLOCK_ADD, {
      threadId,
      messageId,
      blockId,
      block: newBlock, // Send the complete block data
    });

    sendToRenderer(EventNames.MCP_ACTION_PHASE, {
      threadId,
      messageId,
      phase: toolCall.status === "pending" ? "starting" : "completed",
    });

    return blockId;
  }

  /**
   * Update tool call block
   */
  async updateToolCallBlock(
    messageId: string,
    threadId: string,
    blockId: string,
    toolCall: ToolCallResult,
  ): Promise<void> {
    const sequentialContent = await this.getSequentialContent(messageId);
    if (!sequentialContent) return;

    const blockIndex = sequentialContent.blocks.findIndex(
      (b) => b.id === blockId,
    );
    if (blockIndex === -1) return;

    const block = sequentialContent.blocks[blockIndex];
    if (block.type !== "tool_call") return;

    const updatedBlock: ToolCallContentBlock = {
      ...block,
      toolCall,
      isExecuting:
        toolCall.status === "pending" ||
        toolCall.status === "awaiting_approval",
      timestamp: Date.now(),
    };

    sequentialContent.blocks[blockIndex] = updatedBlock;
    await this.saveSequentialContent(messageId, sequentialContent);

    sendToRenderer(EventNames.CONTENT_BLOCK_UPDATE, {
      threadId,
      messageId,
      blockId,
      updateData: { toolCall, isExecuting: updatedBlock.isExecuting },
    });

    const phase = toolCall.status === "pending" ? "executing" : "completed";
    sendToRenderer(EventNames.MCP_ACTION_PHASE, {
      threadId,
      messageId,
      phase,
    });
  }

  /**
   * Mark sequential content as complete
   */
  async completeSequentialContent(
    messageId: string,
    threadId: string,
  ): Promise<void> {
    const sequentialContent = await this.getSequentialContent(messageId);
    if (!sequentialContent) return;

    sequentialContent.isComplete = true;
    sequentialContent.currentBlockId = undefined;

    await this.saveSequentialContent(messageId, sequentialContent);

    sendToRenderer(EventNames.SEQUENTIAL_CONTENT_COMPLETE, {
      threadId,
      messageId,
      totalBlocks: sequentialContent.blocks.length,
    });
  }

  /**
   * Find the block ID for a tool call in sequential content
   */
  async findToolCallBlockId(
    messageId: string,
    toolCallId: string,
  ): Promise<string | null> {
    try {
      const sequentialContent = await this.getSequentialContent(messageId);
      if (!sequentialContent) return null;

      const block = sequentialContent.blocks.find(
        (block) =>
          block.type === "tool_call" &&
          block.toolCall &&
          block.toolCall.id === toolCallId,
      );

      return block?.id || null;
    } catch (error) {
      logger.error("Failed to find tool call block ID", {
        messageId,
        toolCallId,
        error: extractErrorMessage(error),
      });
      return null;
    }
  }

  /**
   * Get sequential content for a message
   */
  private async getSequentialContent(
    messageId: string,
  ): Promise<SequentialMessageContent | null> {
    try {
      const message = await this.getMessageById(messageId);
      if (!message?.contentBlocks) return null;

      return JSON.parse(message.contentBlocks) as SequentialMessageContent;
    } catch {
      return null;
    }
  }

  /**
   * Save sequential content to message
   */
  private async saveSequentialContent(
    messageId: string,
    content: SequentialMessageContent,
  ): Promise<void> {
    await this.updateMessage(messageId, {
      contentBlocks: JSON.stringify(content),
    });
  }

  /**
   * Add a block to sequential content
   */
  private async addBlock(
    messageId: string,
    block: ContentBlock,
  ): Promise<void> {
    let sequentialContent = await this.getSequentialContent(messageId);

    if (!sequentialContent) {
      sequentialContent = {
        blocks: [],
        isComplete: false,
      };
    }

    sequentialContent.blocks.push(block);
    sequentialContent.currentBlockId = block.id;

    await this.saveSequentialContent(messageId, sequentialContent);
  }

  /**
   * Get the next block order number
   */
  private async getNextBlockOrder(messageId: string): Promise<number> {
    const sequentialContent = await this.getSequentialContent(messageId);
    if (!sequentialContent || sequentialContent.blocks.length === 0) {
      return 0;
    }

    return Math.max(...sequentialContent.blocks.map((b) => b.order)) + 1;
  }
}
